﻿namespace NJQuery {
	using System;
	using System.ComponentModel;
	using System.Globalization;

	/// <summary>
	/// Defines the number of months displayed by the Datepicker jQuery UI widget.
	/// </summary>
	[TypeConverter(typeof(NumberOfMonthsConverter))]
	public struct NumberOfMonths : IEquatable<NumberOfMonths>, IComplexProperty {


		#region Static Readonly Fields: Standard values

		/// <summary>
		/// Renders as a single month (1 row, 1 column).
		/// </summary>
		public static readonly NumberOfMonths Single = new NumberOfMonths();

		#endregion


		#region Fields

		private byte shiftedRows;
		private byte shiftedColumns;
		private bool isMatrix;

		#endregion


		#region Static Methods

		/// <summary>
		/// Converts a string representation to its <see cref="NumberOfMonths"/> equivalent.
		/// </summary>
		/// <param name="text">A <see cref="String"/> containing the value to convert.</param>
		/// <param name="result">The parsed value when the conversion succeeds, or a default instance otherwise.</param>
		/// <returns>Indicates whether the conversion succeeded.</returns>
		public static bool TryParse(string text, out NumberOfMonths result) {

			if(string.IsNullOrEmpty(text)) {
				result = Single;
				return false;
			}

			int intValue;
			if(int.TryParse(text, out intValue)) {
				result = new NumberOfMonths(intValue);
				return true;
			}

			string[] splitText = text.Split(',');
			if(splitText.Length == 2) {
				int rows; int columns;
				if(int.TryParse(splitText[0].Trim(), out rows) && int.TryParse(splitText[1].Trim(), out columns)) {
					result = new NumberOfMonths(rows, columns);
					return true;
				}
			}

			result = Single;
			return false;
		}

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new <see cref="NumberOfMonths"/> value using only columns.
		/// </summary>
		/// <param name="count">The number of months to display (as columns).</param>
		/// <exception cref="ArgumentOutOfRangeException">Value must be between 1 and 256.</exception>
		public NumberOfMonths(int count) {
			if(count < 1 || count > 256) throw new ArgumentOutOfRangeException("count", count, "Value must be between 1 and 256.");

			this.shiftedColumns = (byte)(count - 1);
			shiftedRows = 0;
			isMatrix = false;
		}

		/// <summary>
		/// Initializes a new <see cref="NumberOfMonths"/> value using both rows and columns.
		/// </summary>
		/// <param name="rows">The number of rows to display.</param>
		/// <param name="columns">The number of columns to display.</param>
		/// <exception cref="ArgumentOutOfRangeException">Values must be between 1 and 256.</exception>
		public NumberOfMonths(int rows, int columns) {
			if(rows < 1 || rows > 256) throw new ArgumentOutOfRangeException("rows", rows, "Value must be between 1 and 256.");
			if(columns < 1 || rows > 256) throw new ArgumentOutOfRangeException("columns", columns, "Value must be between 1 and 256.");

			this.shiftedRows = (byte)(rows - 1);
			this.shiftedColumns = (byte)(columns - 1);
			isMatrix = true;
		}

		#endregion


		#region Properties

		/// <summary>
		/// Gets the number of rows to display.
		/// </summary>
		public int Rows {
			get { return (int)shiftedRows + 1; }
		}

		/// <summary>
		/// Gets the number of columns to display.
		/// </summary>
		public int Columns {
			get { return (int)shiftedColumns + 1; }
		}

		/// <summary>
		/// Gets the number of months to display.
		/// </summary>
		public int Count {
			get { return ((int)shiftedColumns + 1) * ((int)shiftedRows + 1); }
		}

		/// <summary>
		/// Gets whether or not the value will be rendred as a two-element array for a matrix of months.
		/// </summary>
		public bool IsMatrix {
			get { return isMatrix; }
		}

		/// <summary>
		/// Gets whether or not the value represents a single month (1 row, 1 column).
		/// </summary>
		public bool IsSingle {
			get { return shiftedRows == 0 && shiftedColumns == 0; }
		}

		#endregion


		#region Methods: Object management

		/// <summary>
		/// Determines whether or not the specified object is equal to the current <see cref="NumberOfMonths"/> value.
		/// </summary>
		/// <param name="obj">The <see cref="Object"/> to compare.</param>
		/// <returns>Whether or not the provided object is equal.</returns>
		public override bool Equals(object obj) {
			if(obj is NumberOfMonths) return this == (NumberOfMonths)obj;
			return false;
		}

		/// <summary>
		/// Determines whether or not the specified NumberOfMonths is equal to the current <see cref="NumberOfMonths"/> value.
		/// </summary>
		/// <param name="other">The <see cref="NumberOfMonths"/> to compare.</param>
		/// <returns>Whether or not the provided value is equal.</returns>
		public bool Equals(NumberOfMonths other) {
			return this == other;
		}

		/// <summary>
		/// Serves as a hash function for <see cref="NumberOfMonths"/>.
		/// </summary>
		/// <returns>The hash value associated with the contained data.</returns>
		public override int GetHashCode() {
			if(isMatrix) return (int)shiftedRows << 8 + (int)shiftedColumns << 16;
			return shiftedColumns;
		}

		/// <summary>
		/// Returns a <see cref="String"/> that represents the current <see cref="NumberOfMonths"/> value.
		/// </summary>
		/// <returns>The string representation.</returns>
		public override string ToString() {
			if(isMatrix) return string.Format(CultureInfo.InvariantCulture, "{0},{1}", (int)shiftedRows + 1, (int)shiftedColumns + 1);
			return ((int)shiftedColumns + 1).ToString(CultureInfo.InvariantCulture);
		}

		/// <summary>
		/// Returns a literal JavaScript value used to represent the server value on the client.
		/// </summary>
		/// <returns>The client script value.</returns>
		public string ToClientSource() {
			if(isMatrix) return new int[] { (int)shiftedRows + 1, (int)shiftedColumns + 1 }.ToClientSource();
			return ((int)shiftedColumns + 1).ToClientSource();
		}

		#endregion


		#region Operators

		/// <summary>
		/// Compares the equality of two <see cref="NumberOfMonths"/> values.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are equal.</returns>
		public static bool operator ==(NumberOfMonths value0, NumberOfMonths value1) {
			return value0.shiftedRows == value1.shiftedRows && value0.shiftedColumns == value1.shiftedColumns && value0.isMatrix == value1.isMatrix;
		}

		/// <summary>
		/// Compares the inequality of two <see cref="NumberOfMonths"/> values.
		/// </summary>
		/// <param name="value0">The first value.</param>
		/// <param name="value1">The second value.</param>
		/// <returns>Whether or not the two values are distinct.</returns>
		public static bool operator !=(NumberOfMonths value0, NumberOfMonths value1) {
			return value0.shiftedRows != value1.shiftedRows || value0.shiftedColumns != value1.shiftedColumns || value0.isMatrix != value1.isMatrix;
		}

		#endregion


	}

}